
window.top.Historics.SearchIndex = {
	IDLE: 0,
	BUILDING: 1,
	READY: 2,
	status: 0,
	/* 
	initialize stuff
	*/
	setup: function(onComplete){
		/* get index from disk 
		
		//TODO: The index building will probably have to be done in a C++ service, and all 
		// searches will have to use that; the index built should be serialized for later use too
		try{
			var se = 
				window.top.Historics.getClassLoader().wrappedJSObject.
					getSearchIndex(window.top.Historics.SearchIndex.getCacheFolder().path);
			alert(se.getMessage());
		}catch (e) {
	        alert(e);
	    }*/
		window.top.Historics.SearchIndex.index = {};
		window.top.Historics.SearchIndex.pages = {};
		window.setTimeout(
			function(){
				window.top.Historics.SearchIndex.buildIndex(onComplete);
			}
			, 500
		);
		window.top.Historics.SearchIndex.onPageCachedListeners = new Array();
		/* save index every so often */
		//Historics.SearchIndex.saveIndexTimer = window.setInterval("Historics.SearchIndex.serializeIndex();", 
			//Historics.preferences.getIntPref("saveindexinterval") * 1000);
	},
	/*
	get ready for shutdown
	*/
	teardown: function(){
		/* stop auto index saving */
		//if(Historics.SearchIndex.saveIndexTimer) window.clearInterval(Historics.SearchIndex.saveIndexTimer);
		/* save the index one last time */
		//Historics.SearchIndex.serializeIndex();
	},
	search: function(query){
		var pages = new Array();
		var queryWords = window.top.Historics.SearchIndex.tokenizeHTML(query);
		//window.alert(queryWords.length);
		if(!queryWords || 
			(queryWords.length==1 && queryWords[0]=="") ||
			queryWords.length<=0){
			for(var key in window.top.Historics.SearchIndex.pages) pages.push(window.top.Historics.SearchIndex.pages[key]);			
		}else{
			for(i=0; i<queryWords.length; i++){
				var resultNodes = window.top.Historics.SearchIndex.index[queryWords[i]];
				for(var key in resultNodes){
					pages.push(window.top.Historics.SearchIndex.pages[key]);
				}
			}
		}
		//window.alert(pages.length);
		pages.sort(window.top.Historics.SearchIndex.sortPageResults);
		return pages;
	},
	sortPageResults: function(a, b){
		/** TODO: we should also consider the rank of the page **/
		return b.lastUpdated - a.lastUpdated;
	},
	/**
	cache the current document, and make sure the current uri matches the passed in one. We don't 
	cache pages right when they are loaded, because we want to make sure the user is on there for a while.
	this prevents unecessary caching, and it also makes sure screenshots will have content because the page has
	loaded properly
	*/
	cache: function(doc){
		/* make sure the current uri matches the uri requested to be cached, otherwise we
		are not able to cache this page, the user has navigated away.
		*/
		if(!window.top.Historics.isDocActive(doc)) return;
				
		/* get the current time, used to generate a key that's unique for this doc today */
	  	var now = new Date();
  		var todayStr = (now.getMonth()+1)+"-"+now.getDate()+"-"+now.getFullYear();
		var key = md5(todayStr + doc.location.href);
		
		var cachedDoc = window.top.Historics.SearchIndex.getCachedDoc(key);
		/* if lastUpdated is set, the file alread existed. See if it's still fresh */
		if(cachedDoc.lastUpdated){
			//var minutesElapsed = (now.getTime() - cachedDoc.lastUpdated)/(1000*60);
			/* If it's not more than the preference mincachewait old, we're done here */
			//if(minutesElapsed<Historics.preferences.getIntPref("mincachewait")) return;
			//TODO: For now, we'll only index pages once per day. We may keep this...
			return;
		}
		/* set the properties of this document */
		cachedDoc.title = doc.title;
		cachedDoc.url = doc.location.href;
		cachedDoc.lastUpdated = now.getTime();
		
		/* build a string to index from the document */
		var cacheStr = doc.title + " " + doc.body.innerHTML;
		for(i=0; i<doc.defaultView.frames.length; i++){
			cacheStr += " " + doc.defaultView.frames[i].document.body.innerHTML;
		}
		/* obtain an ordered array, with duplicates, of the words in the index string */
		var words = window.top.Historics.SearchIndex.tokenizeHTML(cacheStr);
		/* build an index of the words */
		cachedDoc.index = {};
		for(i=0; i<words.length; i++){
			if(words[i]=="" || words[i].match(/^\d+$/)) continue;
			/* make a node at this word if there isn't one already */			
			if(typeof(cachedDoc.index[words[i]]) == 'undefined'){
				cachedDoc.index[words[i]] = [i];
			}
			/* add another position to the node */
			else{
				try{
					cachedDoc.index[words[i]].push(i);
				}catch(e){
					dump("Unable to push " + words[i] + " b/c " + cachedDoc.index[words[i]]);
				}
			}
		}
		window.top.Historics.SearchIndex.saveCachedDoc(cachedDoc);
		var thumbFile = window.top.Historics.SearchIndex.getCachedDocThumbnailFile(cachedDoc.key);
		window.top.Historics.Thumbnail.saveThumbnail(thumbFile, doc, null);//window.top.Historics.preferences.getIntPref('maxthumbsize'));
		window.top.Historics.SearchIndex.addCachedDocToIndex(cachedDoc);
		/* let the listeners know a new page has been cached */
		for(var key in window.top.Historics.SearchIndex.onPageCachedListeners){
			window.top.Historics.SearchIndex.onPageCachedListeners[key]();
		}
	},
	/*
	
	*/
	buildIndex: function(onComplete){
		if(window.top.Historics.SearchIndex.status == window.top.Historics.SearchIndex.BUILDING) return;
		window.top.Historics.SearchIndex.status = window.top.Historics.SearchIndex.BUILDING;
		//window.alert("Going to build the index");
		var entries = window.top.Historics.SearchIndex.getCacheFolder().directoryEntries;
		var cachedDoc;
		while(entries.hasMoreElements()){
			var entry = entries.getNext();
			entry.QueryInterface(Components.interfaces.nsIFile);
			if(entry.isDirectory() || !entry.path.match(/\.json$/)) continue;

			cachedDoc = window.top.Historics.SearchIndex.getCachedDocFromFile(entry);			
			if(cachedDoc!=null) window.top.Historics.SearchIndex.addCachedDocToIndex(cachedDoc);
		}
		if(onComplete!=null) onComplete();
		window.top.Historics.SearchIndex.status = window.top.Historics.SearchIndex.READY;
	},
	/*
	
	*/
	addCachedDocToIndex: function(cachedDoc){
		window.top.Historics.SearchIndex.pages[cachedDoc.key] = {
			key: cachedDoc.key,
			title: cachedDoc.title,
			url: cachedDoc.url,
			lastUpdated: cachedDoc.lastUpdated,
			thumbSrc: window.top.Historics.SearchIndex.getCachedDocThumbnailFile(cachedDoc.key).path
		};
		for(var word in cachedDoc.index){
			if(typeof(window.top.Historics.SearchIndex.index[word])=='undefined'){
				window.top.Historics.SearchIndex.index[word] = {}
			}
			if(typeof(window.top.Historics.SearchIndex.index[word][cachedDoc.key])=='undefined'){
				window.top.Historics.SearchIndex.index[word][cachedDoc.key] = {}
			}
			window.top.Historics.SearchIndex.index[word][cachedDoc.key] = cachedDoc.index[word];
		}
	},
	/*
	
	*/
	addOnPageCachedListener: function(listener){
		window.top.Historics.SearchIndex.onPageCachedListeners.push(listener);
	},
	/*
	
	*/
	deserializeIndex: function(){
		var indexFile = window.top.Historics.SearchIndex.getCachedIndexFile();
		try{
			var data = FileIO.read(indexFile);
			window.top.Historics.SearchIndex.index = JSON.parse(data, null);
		}catch(e){
			window.top.Historics.SearchIndex.index = {};
		}
	},
	/*
	save the cache index to disk
	*/
	serializeIndex: function(){
		var indexFileJSON = JSON.stringify(window.top.Historics.SearchIndex.index, null);
		var indexFile = window.top.Historics.SearchIndex.getCachedIndexFile();
		//FileIO.write(indexFile, indexFileJSON, "w");
	},
	/*
	take the contents of a page, remove html, special chars, and tokenize it
	*/
	tokenizeHTML: function(str){
		/* TODO: could make some improvements here. It will index js, css, and probably other stuff still */
		return str
			/* get rid of everything in tags we don't care about */
			.replace(/<(script|style)[^>]*>[^<\/\1>]*?<\/\1>/gi, '')
		
			/* replace html tags and entities with spaces */
			.replace(/(<\/?[^>]+>|&[^;]{1,5};|[\:;,])/gi, ' ')
			
			/* remove leading/trailing spaces */
			.replace(/(^\s*|\s*$)/gi, '')
			
			/* replace all special characters */
			.replace(/[^[a-zA-Z0-9 ,\'\:;]]*/gi, '')
			
			/* make all words lower case */
			.toLowerCase()
			
			/* tokenize the string on whitespace */
			.split(/\s+/g);
	},
	/*
	
	*/
	removeCachedDoc: function(key){
		//this.getCachedDoc(key, doc.location.href);
		/* delete the screenshot of this page if it exists */
		
		/* remove the index entires for this doc from the inverted index */
		
		/* delete the cachedDoc file from disk */
		
		//var file = this.getCachedDocFile(cachedDoc.key);
		//FileIO.unlink(file);
	},
	/*
	
	*/
	indexWords: function(key, words){
		/* go through each word, adding it to the index */
		for(i=0; i<words.length; i++){
			/* make a bucket at this word if there isn't one already */			
			if(typeof(window.top.Historics.SearchIndex.index[words[i]]) == 'undefined')
				window.top.Historics.SearchIndex.index[words[i]] = {};
			
			var node;
			if(typeof(window.top.Historics.SearchIndex.index[words[i]][key]) != 'undefined'){
				node = window.top.Historics.SearchIndex.index[words[i]][key];
				node["pos"].push(i);
			}else{
				node = {
					pos: [i]
				}
			}
			window.top.Historics.SearchIndex.index[words[i]][key] = node;
		}
	},
	/*
	
	*/
	unIndexWords: function(key, words){
		/* go through each word and remove this key */
		for(i=0; i<words.length; i++){
			/* if this bucket dne, move on */
			if(typeof(window.top.Historics.SearchIndex.index[words[i]]) == 'undefined') 
				continue;
			
			/* remove the node if it's there */
			if(typeof(window.top.Historics.SearchIndex.index[words[i]][key]) != 'undefined')
				window.top.Historics.SearchIndex.index[words[i]][key] = null;
			
			/* remove the whole bucket if it's empty */
			if(window.top.Historics.SearchIndex.index[words[i]].length==0)
				window.top.Historics.SearchIndex.index[words[i]] = null;
		}		
	},
	/*
	save the cachedDoc object to disk
	*/
	saveCachedDoc: function(cachedDoc){
		var cachedDocJSON = JSON.stringify(cachedDoc, null);
		var file = window.top.Historics.SearchIndex.getCachedDocFile(cachedDoc.key);
		FileIO.write(file, cachedDocJSON, "w");
	},
	getCachedDocThumbnailFile: function(key){
		var file = window.top.Historics.SearchIndex.getCacheFolder();
		file.append("thumbs");
		file.append(key + ".png");
		return file;
	},
	getCachedDocFromFile: function(file){
		try{
			return JSON.parse(FileIO.read(file), null);
		}catch(e){
			return null;
		}
	},
	/*
	get a CachedDoc object from file or create a new one if it doesn't exist
	*/
	getCachedDoc: function(key){
		var file = window.top.Historics.SearchIndex.getCachedDocFile(key);
		var cachedDoc;
		if((cachedDoc = window.top.Historics.SearchIndex.getCachedDocFromFile(file))==null){
			cachedDoc = {};
			cachedDoc.key = key;
		}
		return cachedDoc;
	},
	/*
	
	*/
	getCachedDocFilename: function(key){
		var file = window.top.Historics.SearchIndex.getCacheFolder();
		file.append(key + ".json");
	},
	/*
	
	*/
	getCachedDocFile: function(key){
		var file = window.top.Historics.SearchIndex.getCacheFolder();
		file.append(key + ".json");
		if(!file.exists()){
			FileIO.create(file);
		}
		return file;
	},
	/*
	
	*/
	getCachedIndexFile: function(){
		var file = window.top.Historics.SearchIndex.getCacheFolder();
		file.append("invertedindex.json");
		if(!file.exists()){
			FileIO.create(file);
		}
		return file;
	},
	/*
	get the cache folder object, create it if necessarys
	*/
	getCacheFolder: function(){
		var cache = Components.classes["@mozilla.org/file/directory_service;1"]
                     .getService(Components.interfaces.nsIProperties)
                     .get("ProfLD", Components.interfaces.nsIFile);
		cache.append("Historics");
		cache.append("Cache");
		if(!cache.exists() || !cache.isDirectory()){
			DirIO.create(cache);
		}
		return cache;
	}
}
